/**
 *
 */
package com.angel.architecture.services.locator.strategies.impl;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

import com.angel.architecture.exceptions.NonBusinessException;
import com.angel.architecture.initializer.injector.DependecyInjector;
import com.angel.architecture.initializer.types.TypeInferencer;
import com.angel.architecture.services.locator.strategies.ServiceLocatorStrategy;
import com.angel.common.helpers.ReflectionHelper;

/**
 * @author William
 *
 */
public class AngelServiceLocatorStrategy implements ServiceLocatorStrategy{

	private static final Logger LOGGER = Logger.getLogger(AngelServiceLocatorStrategy.class);

	private Map<Class<?>, Object> servicesCacheMap;
	private Map<Class<?>, Object> currentlyBuildingMap;


	@SuppressWarnings("unchecked")
	public <T> T locate(Class<T> serviceClass, boolean searchAmongNotFinishedOnes) {
		if (serviceClass == null) {
			throw new NonBusinessException("Service class must be specified");
		}

		if (!(this.servicesCacheMap.containsKey(serviceClass))) {
			if ((searchAmongNotFinishedOnes) && (this.currentlyBuildingMap.containsKey(serviceClass))) {
				return (T) this.currentlyBuildingMap.get(serviceClass);
			}
			this.servicesCacheMap.put(serviceClass, instanciateService(serviceClass));
		}
		return (T) this.servicesCacheMap.get(serviceClass);
	}

	public AngelServiceLocatorStrategy()
	{
		this.servicesCacheMap = new HashMap<Class<?>, Object>();
		this.currentlyBuildingMap = new HashMap<Class<?>, Object>();
	}

	@SuppressWarnings("unchecked")
	private <T extends Object> T instanciateService(Class<T> serviceClass)
	{
		try {
			validateService(serviceClass);

			LOGGER.debug("Service implementation not found in cache. Creating service of class: " + serviceClass);
			Object service = ReflectionHelper.createObject(serviceClass);

			this.currentlyBuildingMap.put(serviceClass, service);
			DependecyInjector.autoInject(service);
			this.currentlyBuildingMap.remove(serviceClass);

			return (T) service;
		} catch (Exception e) {
			throw new NonBusinessException("Cannot instanciate service of class " + serviceClass.getName(), e);
		}
	}

	private void validateService(Class<?> serviceClass) {
		Method[] methods = serviceClass.getMethods();

		Map<String, Collection<Integer>> methodSignatures = new HashMap<String, Collection<Integer>>();
		for (Method method : methods) {
			String name = method.getName();
			Integer parameterQty = Integer.valueOf(method.getParameterTypes().length);

			if (methodSignatures.containsKey(name)) {
				Collection<Integer> parametersQtyCollection = methodSignatures.get(name);
				if (parametersQtyCollection.contains(parameterQty))
					throw new NonBusinessException("Cannot user parameter type overload in service methods. Service [" + serviceClass.getName() + "] method [" + name + "]");

				parametersQtyCollection.add(parameterQty);
			} else {
				Collection<Integer> col = new ArrayList<Integer>();
				col.add(parameterQty);
				methodSignatures.put(name, col);
			}
		}
	}

	@SuppressWarnings("unchecked")
	public <T> T locate(String serviceName, boolean searchAmongNotFinishedOnes) {
		for(Class<?> clazz: this.servicesCacheMap.keySet()){
			if(clazz.getSimpleName().equalsIgnoreCase(serviceName)){
				return (T) this.servicesCacheMap.get(clazz);
			}
		}
		if(searchAmongNotFinishedOnes) {
			for(Class<?> clazz: this.currentlyBuildingMap.keySet()){
				if(clazz.getSimpleName().equalsIgnoreCase(serviceName)){
					return (T) this.currentlyBuildingMap.get(clazz);
				}
			}
		}
		TypeInferencer typeInferencer = new TypeInferencer();
		Class<?> serviceClass = typeInferencer.getClassWithSimpleName(serviceName);
		this.servicesCacheMap.put(serviceClass, instanciateService(serviceClass));
		return (T) this.servicesCacheMap.get(serviceClass);
	}
}
